﻿using System;
using System.Diagnostics;
using log4net;
using Needle.Bus.Engine;
using Needle.Bus.Engine.Factories;
using Needle.Bus.Messages;
using Needle.Utils;
using Needle.Utils.Extensions;
using System.Threading;

namespace Needle.Bus
{
    public class Bus : IDisposable, IBus
    {

        #region static

        private static ILog _log = LogManager.GetLogger(typeof(Bus));

        public static void Start(string busId)
        {
            Start(AppFileSystem.ConfigFile.FullName, busId);
        }

        public static void Start(string configFilePath, string busId)
        {
            try
            {
                _log.InfoFormat("Starting bus '{0}' from configuration file {1}", busId, configFilePath);
                IFactory<Engine.Engine> engineFactory = new EngineFactory(configFilePath);
                Engine.Engine engine = engineFactory.Create(busId);
                Bus bus = new Bus(engine);
                bus.SetCurrent();
                engine.Open();
                _log.InfoFormat("Started bus '{0}'", busId);
            }
            catch (Exception ex)
            {
                _log.Error(String.Format("Failed to start bus '{0}'", busId), ex);
                Stop();
                throw;
            }
        }

        public static void Stop()
        {
            Stop(60.Seconds());
        }

        public static void Stop(TimeSpan timeout)
        {
            var bus = Bus.Context;
            if (bus != null)
            {
                try
                {
                    _log.InfoFormat("Stopping bus '{0}'", bus.Engine.Id);
                    bus.Engine.Close(timeout);
                    _log.InfoFormat("Stopped bus '{0}'", bus.Engine.Id);

                }
                catch (Exception ex)
                {
                    _log.Error(String.Format("Failed to stop bus '{0}'", bus.Engine.Id), ex);
                }
                finally
                {
                    ((IDisposable)bus).Dispose();
                }
            }
        }

        public static void Send(params IMessage[] args)
        {
            Bus.Current.Send(args);
        }

        public static void Send(string address, params IMessage[] messages)
        {
            Bus.Current.Send(address, messages);
        }

        #endregion

        #region thread static

        [ThreadStatic]
        private static Bus _context;

        internal static Bus Context
        {
            get { return _context; }
        }

        public static IBus Current
        {
            get { return _context; }
        }

        #endregion

        private Bus _oldBus;

        public string Id { get { return Engine.Id; } }

        internal Engine.Engine Engine { get; private set; }

        internal IStage Stage { get; private set; }

        #region constructors

        internal Bus(Engine.Engine engine)
            : this(engine, null) { }

        internal Bus(Engine.Engine engine, IStage stage)
        {
            if (engine == null)
                throw new ArgumentNullException("engine", "engine is null.");
            Engine = engine;
            Stage = stage;
        }

        #endregion

        #region IBus

        void IBus.StartStage(string id)
        {
            Engine.StartStage(id);
        }

        void IBus.StartStage(string id, IMessageHandler handler)
        {
            Engine.StartStage(id, handler);
        }

        void IBus.StopStage(string id)
        {
            Engine.StopStage(id);
        }

        void IBus.Send(params IMessage[] messages)
        {
            Engine.Send(BusAddress.Router, messages);
        }

        void IBus.Send(string address, params IMessage[] messages)
        {
            Engine.Send(address, messages);
        }

        #endregion

        internal Bus SetCurrent()
        {
            _oldBus = _context;
            _context = this;
            return this;
        }

        public override string ToString()
        {
            return "Bus " + Id;
        }

        void IDisposable.Dispose()
        {
            if (_context == this)
                _context = _oldBus;
            else
                Debug.Assert(false, String.Format("Bus {0}: unbalanced SetCurrent() and Dispose() calls.", this.Id));
        }

    }
}

